//
// Copyright © 2025 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//

import { IPCMainExposed, MenuBarAction } from './types'
import { isMenuBarAction } from './typesUtils'
import { TitleBarMenuState } from './titleBarMenuState'
import { type ThemeVariantType } from '@hcengineering/theme'

const ToggleMinimizeToTrayAction: MenuBarAction = 'toggle-minimize-to-tray'
const ToggleAutoLaunchAction: MenuBarAction = 'toggle-auto-launch'

const LabelMinimizeToTrayEnabled = '☑ Minimize to tray'
const LabelMinimizeToTrayDisabled = '☐ Minimize to tray'

const LabelAutoLaunchEnabled = '☑ Launch at Login'
const LabelAutoLaunchDisabled = '☐ Launch at Login'

export async function setupTitleBarMenu (ipcMain: IPCMainExposed, root: HTMLElement): Promise<MenuBar> {
  const themeManager = new ThemeManager()
  const minimizeToTrayEnabled = await ipcMain.isMinimizeToTrayEnabled()
  const isAutoLaunchEnabled = await ipcMain.isAutoLaunchEnabled()
  const menuManager = new MenuBarManager(root, minimizeToTrayEnabled, isAutoLaunchEnabled)

  const menuBar = menuManager.getView()

  const menuContainer = root.querySelector('.desktop-app-menu-container')
  if (menuContainer != null) {
    const existingMenuBar = menuContainer.querySelector('.desktop-app-menu-bar')
    if (existingMenuBar != null) {
      existingMenuBar.remove()
    }
    menuContainer.appendChild(menuBar)
  }

  menuManager.attachEventListeners(ipcMain)

  ipcMain.onMinimizeToTraySettingChanged((enabled) => {
    const toggle = root.querySelector(`[data-action="${ToggleMinimizeToTrayAction}"]`)
    if (toggle != null) {
      toggle.textContent = enabled ? LabelMinimizeToTrayEnabled : LabelMinimizeToTrayDisabled
    }
  })

  ipcMain.onAutoLaunchSettingChanged((enabled) => {
    const toggle = root.querySelector(`[data-action="${ToggleAutoLaunchAction}"]`)
    if (toggle != null) {
      toggle.textContent = enabled ? LabelAutoLaunchEnabled : LabelAutoLaunchDisabled
    }
  })

  ipcMain.onWindowStateChange((_event, state) => {
    const maximizeButton = root.querySelector('#maximize-button')
    if (maximizeButton != null) {
      if (state === 'maximized') {
        maximizeButton.textContent = '❐'
      } else {
        maximizeButton.textContent = '□'
      }
    }
  })

  return new MenuBar(themeManager)
}

export class MenuBar {
  private static readonly lastUsedThemeKey = 'last-used-theme'

  constructor (private readonly theme: ThemeManager) {
  }

  public setTheme (theme: ThemeVariantType): void {
    localStorage.setItem(MenuBar.lastUsedThemeKey, theme)
    this.theme.setTheme(theme)
  }

  public lastUsedThemeIsUnknown (): boolean {
    return localStorage.getItem(MenuBar.lastUsedThemeKey) == null
  }
}

export function buildHulyApplicationMenu (minimizeToTrayEnabled: boolean, autoLaunchEnabled: boolean): HTMLElement {
  const menuBuilder = new MenuBuilder()

  const MenuFileIndex = 0
  menuBuilder.addTopLevelMenu('File', 'f')
    .addMenuItem(MenuFileIndex, 'Settings', 'settings', undefined, 's')
    .addMenuItem(MenuFileIndex, 'Select Workspace', 'select-workspace', undefined, 'w')
    .addMenuItem(MenuFileIndex, 'Logout', 'logout', undefined, 'l')
    .addSeparator(MenuFileIndex)
    .addMenuItem(MenuFileIndex, 'Exit', 'exit', 'Alt+F4', 'x')

  const MenuEditIndex = 1
  menuBuilder.addTopLevelMenu('Edit', 'e')
    .addMenuItem(MenuEditIndex, 'Undo', 'undo', 'Ctrl+Z', 'u')
    .addMenuItem(MenuEditIndex, 'Redo', 'redo', 'Ctrl+Y', 'r')
    .addSeparator(MenuEditIndex)
    .addMenuItem(MenuEditIndex, 'Cut', 'cut', 'Ctrl+X', 't')
    .addMenuItem(MenuEditIndex, 'Copy', 'copy', 'Ctrl+C', 'c')
    .addMenuItem(MenuEditIndex, 'Paste', 'paste', 'Ctrl+V', 'p')
    .addMenuItem(MenuEditIndex, 'Delete', 'delete', 'Delete', 'd')
    .addSeparator(MenuEditIndex)
    .addMenuItem(MenuEditIndex, 'Select All', 'select-all', 'Ctrl+A', 'a')

  const MenuViewIndex = 2
  menuBuilder.addTopLevelMenu('View', 'v')
    .addMenuItem(MenuViewIndex, 'Reload', 'reload', 'Ctrl+R', 'r')
    .addMenuItem(MenuViewIndex, 'Force Reload', 'force-reload', 'Ctrl+Shift+R', 'o')
    .addMenuItem(MenuViewIndex, 'Toggle Developer Tools', 'toggle-devtools', 'Ctrl+Shift+I', 'd')
    .addSeparator(MenuViewIndex)
    .addMenuItem(MenuViewIndex, 'Zoom In', 'zoom-in', 'Ctrl+\'+\'', 'i')
    .addMenuItem(MenuViewIndex, 'Zoom Out', 'zoom-out', 'Ctrl+\'-\'', 'u')
    .addMenuItem(MenuViewIndex, 'Actual Size', 'restore-size', 'Ctrl+0', 'a')
    .addSeparator(MenuViewIndex)
    .addMenuItem(MenuViewIndex, 'Toggle Fullscreen', 'toggle-fullscreen', 'F11', 'l')

  const MenuWindowIndex = 3

  const ToggleMinimizeToTrayLabel = minimizeToTrayEnabled ? LabelMinimizeToTrayEnabled : LabelMinimizeToTrayDisabled
  const ToggleAutoLaunchLabel = autoLaunchEnabled ? LabelAutoLaunchEnabled : LabelAutoLaunchDisabled
  menuBuilder.addTopLevelMenu('System', 's')
    .addMenuItem(MenuWindowIndex, ToggleMinimizeToTrayLabel, 'toggle-minimize-to-tray', undefined, 'm')
    .addMenuItem(MenuWindowIndex, ToggleAutoLaunchLabel, 'toggle-auto-launch', undefined, 'a')

  return menuBuilder.build()
}

class ThemeManager {
  private static readonly domThemeKey = 'data-theme'

  public setTheme (theme: ThemeVariantType): void {
    ThemeManager.setTheme(theme)
  }

  public static setTheme (theme: ThemeVariantType): void {
    document.body.setAttribute(ThemeManager.domThemeKey, theme)
  }
}

interface MenuItem {
  type: 'item' | 'separator'
  label?: string
  action?: MenuBarAction
  shortcut?: string
  acceleratorChar?: string
}

interface TopLevelMenu {
  label: string
  accelerator: string
  subMenus: MenuItem[]
}

export class MenuBuilder {
  private readonly menus: TopLevelMenu[] = []

  public addTopLevelMenu (label: string, accelerator: string): this {
    const menu: TopLevelMenu = {
      label,
      accelerator: accelerator.toLowerCase(),
      subMenus: []
    }
    this.menus.push(menu)
    return this
  }

  public addMenuItem (
    topLevelMenuIndex: number,
    label: string,
    action: MenuBarAction,
    shortcut?: string,
    acceleratorChar: string | undefined = undefined
  ): this {
    if (topLevelMenuIndex >= 0 && topLevelMenuIndex < this.menus.length) {
      const item: MenuItem = {
        type: 'item',
        label,
        action,
        shortcut,
        acceleratorChar: (acceleratorChar || label.charAt(0)).toLowerCase()
      }
      this.menus[topLevelMenuIndex].subMenus.push(item)
    }
    return this
  }

  public addSeparator (topLevelMenuIndex: number): this {
    if (topLevelMenuIndex >= 0 && topLevelMenuIndex < this.menus.length) {
      this.menus[topLevelMenuIndex].subMenus.push({ type: 'separator' })
    }
    return this
  }

  public build (): HTMLElement {
    const menuBar = document.createElement('ul')
    menuBar.className = 'desktop-app-menu-bar'

    this.menus.forEach((topLevelMenu) => {
      const topLevelMenuView = document.createElement('li')
      topLevelMenuView.className = 'desktop-app-menu-item'

      const menuButton = document.createElement('button')
      menuButton.className = 'desktop-app-top-menu-button'
      menuButton.dataset.menu = topLevelMenu.label.toLowerCase()
      menuButton.dataset.accelerator = topLevelMenu.accelerator

      const acceleratorSpan = document.createElement('span')
      acceleratorSpan.className = 'desktop-app-accelerator'
      acceleratorSpan.dataset.menu = topLevelMenu.label.toLowerCase()

      const labelText = topLevelMenu.label
      const acceleratorIndex = labelText.toLowerCase().indexOf(topLevelMenu.accelerator)

      if (acceleratorIndex === 0) {
        acceleratorSpan.textContent = topLevelMenu.accelerator.toUpperCase()
        menuButton.appendChild(acceleratorSpan)
        menuButton.appendChild(document.createTextNode(labelText.substring(1)))
      } else if (acceleratorIndex > 0) {
        acceleratorSpan.textContent = topLevelMenu.accelerator.toLowerCase()
        menuButton.appendChild(document.createTextNode(labelText.substring(0, acceleratorIndex)))
        menuButton.appendChild(acceleratorSpan)
        menuButton.appendChild(document.createTextNode(labelText.substring(acceleratorIndex + 1)))
      } else {
        menuButton.textContent = labelText
      }

      const dropdown = document.createElement('div')
      dropdown.className = 'desktop-app-dropdown-menu'
      dropdown.id = `${topLevelMenu.label.toLowerCase()}-menu`

      topLevelMenu.subMenus.forEach(item => {
        if (item.type === 'separator') {
          const separator = document.createElement('div')
          separator.className = 'desktop-app-dropdown-separator'
          dropdown.appendChild(separator)
        } else if (item.type === 'item' && item.label != null) {
          const menuItemButton = document.createElement('button')
          menuItemButton.className = 'desktop-app-dropdown-item'
          menuItemButton.dataset.accelerator = item.acceleratorChar
          menuItemButton.dataset.action = item.action

          const labelSpan = document.createElement('span')

          const itemAcceleratorSpan = document.createElement('span')
          itemAcceleratorSpan.className = 'desktop-app-accelerator'

          if (item.acceleratorChar) {
            const labelParts = this.splitLabelByAccelerator(item.label, item.acceleratorChar)

            const actualChar = item.label.charAt(
              item.label.toLowerCase().indexOf(item.acceleratorChar.toLowerCase())
            )
            itemAcceleratorSpan.textContent = actualChar

            if (labelParts.before) {
              labelSpan.appendChild(document.createTextNode(labelParts.before))
            }
            labelSpan.appendChild(itemAcceleratorSpan)
            if (labelParts.after) {
              labelSpan.appendChild(document.createTextNode(labelParts.after))
            }
          }

          menuItemButton.appendChild(labelSpan)

          if (item.shortcut) {
            const shortcutSpan = document.createElement('span')
            shortcutSpan.className = 'desktop-app-shortcut'
            shortcutSpan.textContent = item.shortcut
            menuItemButton.appendChild(shortcutSpan)
          }

          dropdown.appendChild(menuItemButton)
        }
      })

      topLevelMenuView.appendChild(menuButton)
      topLevelMenuView.appendChild(dropdown)
      menuBar.appendChild(topLevelMenuView)
    })

    return menuBar
  }

  private splitLabelByAccelerator (label: string, acceleratorChar: string): { before: string, after: string } {
    const index = label.toLowerCase().indexOf(acceleratorChar.toLowerCase())
    if (index === -1) {
      return { before: label, after: '' }
    }
    return {
      before: label.substring(0, index),
      after: label.substring(index + 1)
    }
  }
}

class MenuBarManager {
  private readonly state: TitleBarMenuState
  private readonly view: HTMLElement

  private altPressed: boolean = false
  private controlKeysActivated: boolean = false

  private readonly TopMenuStyle = '.desktop-app-top-menu-button'
  private readonly DropdownMenuStyle = '.desktop-app-dropdown-menu'
  private readonly DropdownItemStyle = '.desktop-app-dropdown-item'
  private readonly MenuItemStyle = '.desktop-app-menu-item'

  private readonly StateStyleAltMode = 'desktop-app-alt-mode'
  private readonly StateStyleKeyboardSelected = 'desktop-app-keyboard-selected'
  private readonly StateStyleAltModeActive = 'desktop-app-alt-active'

  constructor (
    private readonly root: HTMLElement,
    minimizeToTrayEnabled: boolean,
    autoLaunchEnabled: boolean
  ) {
    this.state = new TitleBarMenuState(
      () => this.topLevelMenus().length,
      (topLevelMenuIndex: number) => {
        const children = this.childrenOfTopLevelMenu(topLevelMenuIndex)
        return children.length
      }
    )

    this.view = buildHulyApplicationMenu(minimizeToTrayEnabled, autoLaunchEnabled)
  }

  public getView (): HTMLElement {
    return this.view
  }

  private topLevelMenus (): NodeListOf<HTMLButtonElement> {
    return this.root.querySelectorAll<HTMLButtonElement>(this.TopMenuStyle)
  }

  private onButtonClick (id: string, callback: () => void): void {
    const button = this.root.querySelector(`#${id}`)
    if (button) {
      button.addEventListener('click', callback)
    }
  }

  public attachEventListeners (ipcMain: IPCMainExposed): void {
    this.onButtonClick('minimize-button', () => {
      ipcMain.minimizeWindow()
    })

    this.onButtonClick('maximize-button', () => {
      ipcMain.maximizeWindow()
    })

    this.onButtonClick('close-button', () => {
      ipcMain.closeWindow()
    })

    this.onButtonClick('back-button', () => {
      history.back()
    })

    this.onButtonClick('forward-button', () => {
      history.forward()
    })

    document.addEventListener('keydown', (e) => { this.handleKeyDown(ipcMain, e) })
    document.addEventListener('keyup', (e) => { this.handleKeyUp(e) })

    this.topLevelMenus().forEach((button, index) => {
      button.addEventListener('click', (e) => { this.handleTopLevelMenuButtonClick(e, index) })
    })

    document.addEventListener('click', (e) => { this.handleDocumentClick(e) })

    document.querySelectorAll<HTMLButtonElement>(this.DropdownItemStyle + '[data-action]').forEach(item => {
      item.addEventListener('click', () => { this.handleMenuButtonClick(ipcMain, item) })
    })

    ipcMain.onWindowFocusLoss(() => {
      this.state.exitAltMode()
      this.altPressed = false
      this.controlKeysActivated = false
      this.renderState()
    })
  }

  private renderState (): void {
    if (this.state.isAltModeActive) {
      this.root.classList.add(this.StateStyleAltModeActive)
    } else {
      this.root.classList.remove(this.StateStyleAltModeActive)
    }

    this.root.querySelectorAll<HTMLElement>(this.DropdownMenuStyle).forEach(menu => {
      menu.style.display = 'none'
    })

    const topLevelMenus = this.topLevelMenus()

    topLevelMenus.forEach((button, index) => {
      button.classList.remove(this.StateStyleAltMode)

      if (index === this.state.FocusedTopLevelMenuIndex) {
        button.classList.add(this.StateStyleAltMode)
        button.focus()

        if (this.state.isTopLevelMenuExpanded && button.dataset.menu) {
          const menuType = button.dataset.menu
          const dropdown: HTMLElement | null = this.root.querySelector(`#${menuType}-menu`)
          if (dropdown) {
            dropdown.style.display = 'block'
          }
        }
      } else {
        button.blur()
      }
    })

    if (this.state.FocusedTopLevelMenuIndex != null && this.state.isTopLevelMenuExpanded) {
      const candidates = this.childrenOfTopLevelMenu(this.state.FocusedTopLevelMenuIndex)
      candidates.forEach((menu, index) => {
        if (index === this.state.FocusedChildMenuIndex) {
          menu.classList.add(this.StateStyleKeyboardSelected)
        } else {
          menu.classList.remove(this.StateStyleKeyboardSelected)
        }
      })
    }
  }

  private childrenOfTopLevelMenu (index: number): NodeListOf<HTMLElement> {
    const topLevelMenus = this.topLevelMenus()
    const menuButton = topLevelMenus[index]
    const menuType = menuButton.dataset.menu
    const dropdown = this.root.querySelector(`#${menuType}-menu`)
    if (dropdown) {
      return dropdown.querySelectorAll<HTMLElement>(this.DropdownItemStyle)
    }
    return document.createDocumentFragment().querySelectorAll<HTMLElement>('*')
  }

  private executeMenuAction (ipcMain: IPCMainExposed, action: MenuBarAction): void {
    try {
      ipcMain.executeMenuBarAction(action)
    } catch (error) {
      console.error('error executing action:', error)
    }
  }

  private handleKeyDown (ipcMain: IPCMainExposed, e: KeyboardEvent): void {
    if (e.altKey) {
      this.altPressed = true
    }

    if (e.shiftKey || e.ctrlKey || e.metaKey) {
      if (this.altPressed) {
        this.controlKeysActivated = true
        return
      }
    }

    if (e.altKey) {
      if (this.state.isAltModeActive) {
        if (this.state.FocusedTopLevelMenuIndex != null) {
          this.state.closeAll()
          this.renderState()
          return
        }
      } else {
        this.state.enterAltMode(null)
        this.renderState()
      }
    }

    switch (e.key) {
      case 'ArrowLeft':
        this.state.moveFocusHorizontal(-1)
        this.renderState()
        break

      case 'ArrowRight':
        this.state.moveFocusHorizontal(+1)
        this.renderState()
        break

      case 'ArrowDown':
        this.state.moveFocusVertical(+1)
        this.renderState()
        break

      case 'ArrowUp':
        this.state.moveFocusVertical(-1)
        this.renderState()
        break
    }

    switch (e.key) {
      case 'Escape':
        this.state.defocus()
        this.renderState()
        break

      case 'Enter':
      case ' ':
        if (this.state.FocusedTopLevelMenuIndex != null) {
          if (this.state.isTopLevelMenuExpanded) {
            if (this.state.FocusedChildMenuIndex != null) {
              const children = this.childrenOfTopLevelMenu(this.state.FocusedTopLevelMenuIndex)
              const focused = children[this.state.FocusedChildMenuIndex]
              this.state.closeAll()
              this.renderState()
              if (focused.dataset.action && isMenuBarAction(focused.dataset.action)) {
                this.executeMenuAction(ipcMain, focused.dataset.action)
              }
              this.renderState()
            }
          }
        }
        break

      default: {
        const key = e.key.toLowerCase()
        if (!this.state.isAltModeActive) {
          return
        }

        if (this.state.isTopLevelMenuExpanded && this.state.FocusedTopLevelMenuIndex != null) {
          const children = this.childrenOfTopLevelMenu(this.state.FocusedTopLevelMenuIndex)
          for (let i = 0; i < children.length; i++) {
            if (children[i].dataset.accelerator === key) {
              const action = children[i].dataset.action
              if (action) {
                if (isMenuBarAction(action)) {
                  this.executeMenuAction(ipcMain, action)
                }
                this.state.closeAll()
                this.renderState()
                return
              }
            }
          }
        }

        const menuButtons = this.root.querySelectorAll<HTMLButtonElement>(this.TopMenuStyle + '[data-accelerator]')
        for (let i = 0; i < menuButtons.length; i++) {
          if (menuButtons[i].dataset.accelerator === key) {
            this.state.expandTopLevelMenu(i)
            this.state.focusChildMenu()
            this.renderState()
            return
          }
        }
        break
      }
    }
  }

  private handleKeyUp (e: KeyboardEvent): void {
    if (e.key === 'Alt') {
      if (this.controlKeysActivated) {
        this.state.exitAltMode()
        this.renderState()
      } else {
        if (this.state.FocusedTopLevelMenuIndex == null) {
          this.state.enterAltMode(0)
          this.renderState()
        }
      }
      this.controlKeysActivated = false
      this.altPressed = false
    }
  }

  private handleMenuButtonClick (ipcMain: IPCMainExposed, item: HTMLButtonElement): void {
    const action = item.dataset.action
    if (action) {
      if (isMenuBarAction(action)) {
        this.executeMenuAction(ipcMain, action)
      }
      this.state.closeAll()
      this.renderState()
    }
  }

  private handleTopLevelMenuButtonClick (_e: Event, index: number): void {
    this.state.expandTopLevelMenu(index)
    this.renderState()
  }

  private handleDocumentClick (e: Event): void {
    const target = e.target as HTMLElement
    if (!target.closest(this.MenuItemStyle)) {
      this.state.closeAll()
      this.renderState()
    }
  }
}
